using System.Collections.Generic;
using System.Diagnostics;

namespace EternitySolver.Core.Solvers.FixedPath
{
    public abstract class FixedPathSolver : ISolver
    {
        public event Delegates.TilePlaced OnTilePlaced = delegate { };
        public event Delegates.BoardSolved OnSolved = delegate { };

        private LinkedListNode<int> _currentNode;
        public abstract LinkedList<int> GetPath(Board board);

        public void Solve(Board board)
        {
            var path = GetPath(board);
            _currentNode = path.First;
            var position = _currentNode.Value;
            var possibles = new Dictionary<int, List<TileOrientation>>();
            Stopwatch stopwatch = Stopwatch.StartNew();
            do
            {
                // do we have a list of items we are currently trying on this board pos?
                if (possibles.ContainsKey(position))
                {
                    //how many layouts do we have remaining?
                    if (possibles[position].Count == 0)
                    {
                        // oh dear, no options left, better remove the previous tile
                        possibles.Remove(position);
                        position = Rewind(position);
                        board.Tilebag.Add(board.PlacedTiles[position]);
                        board.PlacedTiles[position] = null;
                    }
                    else
                    {
                        // get the next layout
                        var tile = possibles[position][0].Tile;
                        tile.RotateToLayout(possibles[position][0]);
                        possibles[position].RemoveAt(0);

                        // set it down on the board and remove from the bag
                        board.PlacedTiles[position] = tile;
                        board.Tilebag.Remove(tile);

                        // increment the position
                        if (_currentNode.Next != null)
                            position = Forward(position);
                    }
                }
                else
                {
                    // this is a new board position, get the list of possibles
                    // what do we want in this tile space?
                    var specification = board.SpecificationAt(position);
                    var possiblelayouts = board.Tilebag.FindTileLayoutsThatFit(specification);

                    // how many fit the specification?
                    if (possiblelayouts.Count == 0)
                    {
                        // no _possibles, better put the previous tile back into the tile bag and try the next layout
                        possibles.Remove(position);
                        position = Rewind(position);
                        board.Tilebag.Add(board.PlacedTiles[position]);
                        board.PlacedTiles[position] = null;
                    }
                    else
                    {
                        // save for later
                        possibles[position] = possiblelayouts;

                        // get the next layout
                        var tile = possibles[position][0].Tile;
                        tile.RotateToLayout(possibles[position][0]);
                        possibles[position].RemoveAt(0);

                        // set it down on the board and remove from the bag
                        board.PlacedTiles[position] = tile;
                        board.Tilebag.Remove(tile);

                        // increment the position
                        if (_currentNode.Next != null)
                            position = Forward(position);
                    }
                }

                // board updated
                OnTilePlaced(board);
            }
            while (board.Tilebag.Jumbled.Count > 0);
            OnSolved(board, stopwatch.ElapsedMilliseconds);
        }

        public int Forward(int current)
        {
            _currentNode = _currentNode.Next;
            return _currentNode.Value;
        }

        public int Rewind(int current)
        {
            _currentNode = _currentNode.Previous;
            return _currentNode.Value;
        }
    }
}